package com.yfbao.horizon.common;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 1、缓存命中统计
 * 2、循环批量查询时防止溢出监测工具
 */
@Slf4j
public class StatMonitUtil {

    private static Cache<String, Integer> loopStatCache = Caffeine.newBuilder().expireAfterAccess(2, TimeUnit.MINUTES).maximumSize(2000).build();

    //批量执行的循环集合返回的预警上限
    private static Integer maxLength = 50000;
    //批量执行的循环次数预警上限
    private static Integer maxCycleNumber = 100;

    private static Map<String,StatDto> liveCacheMap =new ConcurrentHashMap<>();
    private static List<StatDto> moveCacheMap = Collections.synchronizedList(new ArrayList<StatDto>());

    private static ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(1);
    private static Random random= new Random();

    private static AtomicInteger callTime = new AtomicInteger(0);
    private static TimeUnit LOCAL_TIMEUNIT= TimeUnit.SECONDS;



    static {
        scheduler.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                if(!moveCacheMap.isEmpty()){
                    Iterator<StatDto> iterator = moveCacheMap.iterator();
                    StringBuilder builder = new StringBuilder();
                    builder.append("moveCache size:").append(moveCacheMap.size()).append(";");
                    while (iterator.hasNext()){
                        StatDto next = iterator.next();
                        builder.append("key:").append(next.getKey()).append(":").append(next.getReadTimes().get()).append(":").append(next.getLiveTime()/1000).append("s; ");
                        iterator.remove();
                    }
                    log.info(builder.toString());
                }

                if(!liveCacheMap.isEmpty()){
                    Set<Map.Entry<String, StatDto>> entries = liveCacheMap.entrySet();
                    Iterator<Map.Entry<String, StatDto>> iterator = entries.iterator();
                    StringBuilder builder = new StringBuilder();
                    builder.append("liveCache size:").append(liveCacheMap.size()).append(";");
                    while (iterator.hasNext()){
                        Map.Entry<String, StatDto> next = iterator.next();
                        StatDto value = next.getValue();
                        builder.append("key:").append(next.getKey()).append(":").append(value.getReadTimes().get()).append(":").append((System.currentTimeMillis()-value.getCreateTime())/1000).append("s; ");
                    }
                    log.info(builder.toString());
                }

            }

        },60,10*60, LOCAL_TIMEUNIT);
    }
    //统计对象大小：            ObjectSizeCalculator.getObjectSize(moveCacheMap);


    /**
     * 缓存命名统计
     * @param cacheParm
     * @param cacheName
     */
    public static void cacheTotalStat(Cache cacheParm, String cacheName){
        final Cache cache =cacheParm;
        final String name = cacheName;

        scheduler.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                StringBuilder builder = new StringBuilder();
                builder.append(name+"命中率："+cache.stats().hitRate()).append(";");
                builder.append(name+"命中次数："+cache.stats().hitCount()).append(";");
                builder.append(name+"回收缓存数量："+cache.stats().evictionCount()).append(";");
                builder.append(name+"回收缓存权重："+cache.stats().evictionWeight()).append(";");
                builder.append(name+"平均加载耗时："+cache.stats().averageLoadPenalty()).append(";");
                log.info(builder.toString());
                log.info("加载："+ cache.asMap().size());

            }
        },10+random.nextInt(10),1*10, LOCAL_TIMEUNIT);
    }


    /**
     * 统计新建或跟新缓存
     * @param key
     */
    public static void creatOrUpdCacheStat(String key){
        StatDto statDto = liveCacheMap.get(key);
        if(statDto ==null){
            statDto = new StatDto();
            statDto.setKey(key);
            liveCacheMap.put(key,statDto);
        }
        statDto.getUpdateTimes().incrementAndGet();
        int calls = callTime.incrementAndGet();
        if(calls>1000){
            callTime = new AtomicInteger(0);
            //触发动作 自动LRU
        }
    }

    /**
     * 统计查询缓存
     * @param key
     */
    public static void readCacheStat(String key){
        StatDto statDto = liveCacheMap.get(key);
        if(statDto ==null){
            return;
        }
        statDto.getReadTimes().incrementAndGet();
    }

    /**
     * 统计移除缓存
     * @param key
     */
    public static void moveCacheStat(String key){
        StatDto statDto = liveCacheMap.remove(key);
        if(statDto ==null){
            return;
        }
        statDto.setLiveTime(System.currentTimeMillis()-statDto.getCreateTime());
        moveCacheMap.add(statDto);
    }

    /**
     * 获取循环监测key
     * @return
     */
    public static String getCycleKey(){
        String key = String.valueOf(System.currentTimeMillis());
        String threadName = Thread.currentThread().getName();
        try {
            String className = Thread.currentThread().getStackTrace()[1].getClassName();//调用的类名
            String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();//调用的方法名
            int lineNumber = Thread.currentThread().getStackTrace()[1].getLineNumber();//调用的行数
            key = threadName+":"+className+":"+methodName+":"+lineNumber+":"+key;
        }catch (Throwable t){
            log.error("get loop key exception",t);
            key=threadName+":"+key;
        }
        return key;
    }

//    /**
//     * 监测统计循环批量调用，
//     * 目前 超过预置打印错误日志，输出异常查询点和返回的数据条数
//     * @param collection
//     */
//    public static void loopCheck(Collection collection){
//        String loopKey = getLoopKey();
//        loopCheck(loopKey,collection);
//    }

    /**
     * 监测统计循环批量调用，
     * 目前 超过预置打印错误日志，输出异常查询点和返回的数据条数
     * @param key 需要巡检监测的key
     * @param collection 循环操作的集合
     */
    public static void cycleListMonit(String key, Collection collection){
        if(collection ==null || collection.isEmpty()){
            return;
        }
        Integer length = loopStatCache.getIfPresent(key);
        if(length == null){
            length = 0;
        }
        length = length+collection.size();
        if(length > maxLength){
            log.error(key+" size over maxLength,"+length);
        }
        loopStatCache.put(key,length);
    }


    /**
     * 获取循环检查key，同时监测批量操作列表的长度是否超过监测上限
     * @return
     */
    public static <T> String getCycleKey(List<List<T>> list){
        String cycleKey = getCycleKey();
        if(list !=null && list.size()>maxCycleNumber){
            log.error(cycleKey+" cycle number over maxCycleNumber, "+list.size());
        }
        return cycleKey;
    }

    /**
     * 监测分批处理时的循环次数 监测，
     * 目前 超过预置打印错误日志，输出异常查询点和返回的数据条数
     * @param list 需要巡检次数的批量列表
     */
    public static <T> void cycleMonit(List<List<T>> list){

        if(list !=null && list.size()>maxCycleNumber){
            String cycleKey = getCycleKey();
            log.error(cycleKey+" cycle number over maxCycleNumber, "+list.size());
        }
    }

}
